;************************************************************
; FASM dynamic string library.
;
; (c)2003 John Found
; (c)2003 Mateusz Tymek (aka decard)
; (c)2003 Victor Loh (aka roticv)
;
; You can use and modify this library, as long as modifyed
; versions are clearly marked (author of the modification
; and what is changed), copyright notice is not
; removed and the library remains free.
; Copyright for the library concept and parts written by
; me, remains to me, John Found
; Copyright for the modifyed/new parts remains to their
; authors.
;
; Versions:
;   dd.mm.yyyy  version   author of modification
;     - description
;--------------------------------------------------------
;   09.07.2003  v1.0    John Found
;     - the first public version.
;   15.07.2003  v1.0.1  John Found
;     - minor bug with string table expand. Look in NewStr
;   17.09.2003  v1.1.0  Mateusz Tymek
;     - made all functions stdcall
;     - added StrCat and StrPos, and modified StrLen
;   25.09.2003  v1.1.2  Mateusz Tymek, Victor Loh
;     - added StrLCase, StrUCase, StrCopyMMX, StrInsert
;     - added new NumToStr, old version renamed to _NumStr
;     - some small optimizations & bugfixes
;   26.09.2003 v1.1.3  JohnFound
;     - some bug fixes and style corections.
;   29.09.2003 v1.1.4  John Found, Mateusz Tymek
;     - library rewtiten to use John Found's stdcall macros
;     - added two new low-level NumToStr routines: NumToStrF and NumToStrUF
;     - NumToStr rewriten
;     - some small bugfixes & modifications
;     - added StrExtract
;   01.11.2003 v1.1.4.1 Mateusz Tymek
;     - preserved ecx in StrDel (it was destroyed by API calls)
;   21.11.2003 v1.1.5 John Found
;     - StrComp splitted to 2 functions: StrCompCase and StrCompNoCase
;       IMPORTANT: inversed return result: CARRY = 1 mean strings equal.
;       This is because the using is more natural this way:
;            stdcall  StrCompCase, str1, str2
;            jc       .equal
;   27.11.2003 v1.2 John Found
;     - Added function StrHash, that produces 32bit hash value of the string.
;       The hash function is based on FASM hash function and have to be compatible.
;   03.12.2003 v1.2.3 Mateusz Tymek
;     - Added StrCharPos and StrToNum
;   13.12.2003 v1.2.4 Materusz Tymek
;     - added StrCharCat and StrInsertChar
;   04.01.2005 v1.2.5 John Found
;     - Fixed bug in StrSetLength
;     - Added function StrURLEncode
;*************************************************************************************


;--< How to use it >-----------------------------------------------------------------
; 1. Include "strutils.inc" somewhere in the begining of main file.
; 2. Define structure "StrTable" of type "TStrTable" somewhere in data section.
; 3. Before using of library functions, call "InitStrings"
; 4. After last use of the library (probably on close of application),
;    call "FreeStrings" to free all used memory.
; 5. Some functions are with register parameter passing, other with "stdcall"
;    parameter passing. Read the header descriptions of the functions.
;------------------------------------------------------------------------------------

; Global variable, storing parameters of dynamic strings list.
;uglobal
;  StrTable        TStrTable      ; StrLib library variable.
;endg


; < Library functions >

;************************************************************************************
; Allocates memory for string table and allocates memory for strings.
; Start it before any work with strings.
; Returns 0 if failed to allocate needed memory.
;************************************************************************************
initialize InitStrings
begin
        StrLib = 1

        push    esi ebx
        mov     esi,StrTable
;        invoke  GetProcessHeap
        test    eax,eax
        jz      .finish
;        mov     [StrTable.heap],eax
;        invoke  HeapAlloc,[StrTable.heap],HEAP_ZERO_MEMORY, STR_MINCOUNT * 4
                 push STR_MINCOUNT * 4
                 call getMem
                 add esp,4

        test    eax,eax
        jz      .finish
        mov     [StrTable.ptr],eax
        mov     [StrTable.LastHandle], 0
        mov     [StrTable.count], STR_MINCOUNT
        or      dword [eax], -1
.finish:
        pop     ebx esi
        return
endp

;**************************************************************************************
; Frees all memory used for strings library
; Call it before exit of the program.
;**************************************************************************************
finalize FreeStrings
begin
        push    edi esi ebx
        mov     esi,[StrTable.ptr]
        mov     edi,[StrTable.count]
        xor     ebx,ebx
.freeloop:
        mov     ebx, [esi]
        test    ebx,ebx
        jz      .nxt
        js      .nxt
;        invoke  HeapFree,[StrTable.heap],0,ebx
                 push ebx
                 call freeMem
                 add esp,4

.nxt:
        add     esi,4
        dec     edi
        jnz     .freeloop
;        invoke  HeapFree,[StrTable.heap],0, [StrTable.ptr]
                 push [StrTable.ptr]
                 call freeMem
                 add esp,4

        xor     ebx,ebx
        mov     [StrTable.heap],ebx
        mov     [StrTable.ptr],ebx
        mov     [StrTable.count],ebx
        pop     ebx esi edi
        return
endp

;**************************************************************************************
;  Returns the pointer in memory of the hString, or NULL on error
;**************************************************************************************
proc StrPtr         ; proc StrPtr [hString]
        begin
        mov     eax,[esp+4]             ; mov eax,[hString]
        test    eax,$ffff0000
        jnz     .finish                 ; It's pointer, go to finish.
        cmp     eax,[StrTable.count]
        jae     .notfound
        push    ebx
        mov     ebx, [StrTable.ptr]
        mov     eax, [4*eax+ebx]
        pop     ebx
.finish:
        ret     4
.notfound:
        xor     eax,eax
        ret     4
endp

;**************************************************************************************
;  Creates new empty string and returns handle
;  Return: handle of the new created string.
;**************************************************************************************
proc StrNew
        begin
        push    ebx ecx edx esi edi

; Find first empty place.
        mov     ecx,[StrTable.count]
        mov     ebx,[StrTable.ptr]
        mov     esi,[StrTable.LastHandle]
        xor     edx,edx

.search:
        inc     esi
        cmp     esi, [StrTable.count]
        jne     @f
        xor     esi,esi
        inc     esi
@@:
        cmp     [4*esi+ebx],edx
        je      .found
        dec     ecx
        jnz     .search

.notfound:                              ; expands the table. esi is right.
        mov     esi, [StrTable.count]
        mov     eax, esi
        shl     eax,1
        add     eax, esi
        shr     eax,1                   ; count = count * 1.5
        inc     eax
        mov     [StrTable.count],eax
        shl     eax,2
;        invoke  HeapReAlloc,[StrTable.heap],HEAP_ZERO_MEMORY, [StrTable.ptr],eax
                 push eax
                 push [StrTable.ptr]
                 call regetMem
                 add esp,4*2

        mov     [StrTable.ptr],eax
        mov     ebx, eax                ; 15.07.2003 this added to repair bug with table expanding

.found:                                 ; [4*esi+ebx] points to StrTable entry.
        mov     [StrTable.LastHandle], esi
;        invoke  HeapAlloc, [StrTable.heap], HEAP_ZERO_MEMORY, STR_MINSTRLEN
                 push STR_MINSTRLEN
                 call getMem
                 add esp,4

        mov     dword [eax], 0
        mov     [4*esi+ebx],eax
        mov     eax,esi
        pop     edi esi edx ecx ebx
        return
endp

;**************************************************************************
; Deletes the string if it is possible.
;**************************************************************************
proc StrDel, .hString
        begin
        push    eax ebx ecx edx esi
        mov     esi, [StrTable.ptr]
        mov     eax,[.hString]               ; mov eax,[hString]
        test    eax, eax
        jz      .finish
        test    eax,$ffff0000
        jz      .process
; Find the pointer in the table.
        mov     ecx, [StrTable.count]
        xor     ebx,ebx
.search:
        cmp     [esi+4*ebx],eax
        je      .free
        inc     ebx
        dec     ecx
        jnz     .search
        jmp     .finish

.process:
        mov     ebx, eax
        stdcall StrPtr,eax
        test    eax,eax
        jz      .finish
.free:
;        invoke  HeapFree, [StrTable.heap], 0, eax
                 push eax
                 call freeMem
                 add esp,4

        mov     dword [esi+4*ebx], NULL        ; Set the cell of the table to null.
.finish:
        pop     esi edx ecx ebx eax
        return
endp


;**************************************************************************
; Duplicates given string, and returns a handle to new one
;**************************************************************************
proc StrDup         ; proc StrDup [hBaseStr]
begin
        mov     eax,[esp+4]             ; mov eax,[esp+4]
        or      eax,eax
        jz      .exit
        stdcall StrNew
        stdcall StrCopy, eax,dword[esp+4]    ; stdcall StrCopy, [hBaseStr]
.exit:  ret     4
endp

;**************************************************************************
; Returns the length of the string.
; Arguments:
;  hString - handle or pointer to the string (static or dynamic)
;**************************************************************************
proc StrLen, .hString    ; proc StrLen [hString]
        begin
        push    ebx
        stdcall StrPtr, [.hString]
        mov     ebx,eax
  .scan:
        cmp     byte[eax],0
        lea     eax,[eax+1]
        jne     .scan
        sub     eax,ebx
        dec     eax         ; return value in eax
        pop     ebx
        return
endp

;***************************************************************************
; If the hString is larger than length - do nothing
; If the hString is smaller than length -> set the length of string to length
; returns pointer to the new (old) string
;
; Arguments:
;   hString - string handle. /not pointer!/
;   length - new string length.
; Returns: pointer to the string.
;***************************************************************************
proc StrSetLength, .hString, .length   ; proc StrSetLength [hString], [length]
        begin
        push    ebx ecx edx esi edi     ; esp=esp-20

        mov     ebx, [.length]          ; mov ecx,[length]
        lea     ebx, [ebx+4]            ; the string have to be at least 1 dword bigger than the length specified.

        mov     eax,[.hString]          ; mov eax,[hString]
        test    eax,$ffff0000   ; if eax is pointer then error
        jnz     .error
        cmp     eax,[StrTable.count]    ; invalid handle.
        jge     .error

        mov     edi,[StrTable.ptr]
        lea     edi,[4*eax+edi]         ; points to the address of table pointer of destination string
        mov     esi,[edi]
        test    esi,esi                 ; Error string not defined
        jz      .error                  ;

;        invoke  HeapSize, [StrTable.heap], 0, esi
                 push esi
                 call sizeMem
                 add esp,4

        cmp     eax,-1
        je      .error
        cmp     eax,ebx
        jl      .resize
        mov     eax,esi
        jmp     .finish

.resize:
;        invoke  HeapReAlloc, [StrTable.heap], HEAP_GENERATE_EXCEPTIONS or HEAP_ZERO_MEMORY, esi, ebx
                 push ebx
                 push esi
                 call regetMem
                 add esp,4*2

        mov     [edi], eax
        jmp     .finish

.error:
        xor     eax,eax

.finish:
        pop     edi esi edx ecx ebx
        return
endp


;***************************************************************************************
;  Copyes source to destination string.
;  Arguments:
;     source - destination string (handle only)
;     dest -  source string (handle or pointer)
;***************************************************************************************
proc StrCopy, .dest, .source
        begin
        push    esi edi eax ecx
        mov     edi,[.dest]
        test    edi,$ffff0000   ; if edi is pointer go to finish.
        jnz     .finish
        mov     esi,[.source]
        test    esi,esi
        jz      .finish
        stdcall StrPtr, esi
        mov     esi,eax
        test    esi, esi
        jz      .finish
        stdcall StrLen, eax
        inc     eax
        mov     ecx,eax
        stdcall StrSetLength, edi,eax
        test    eax,eax
        jz      .finish
        stdcall StrPtr, edi
        mov     edi,eax
        cld
        rep movsb               ; copy strings
.finish:
        pop     ecx eax edi esi
        return
endp

;***************************************************************************************
; Compares two strings - case sensitive.
; Returns CARRY = 1 if the strings are equal.
; Returns CARRY = 0 if the strings are different.
;***************************************************************************************
proc StrCompCase, .str1, .str2
begin
        push    eax esi edi

        cmp     [.str1], 0
        je      .noteq
        cmp     [.str2], 0
        je      .noteq

        stdcall StrPtr, [.str1]
        mov     esi,eax
        stdcall StrPtr, [.str2]
        mov     edi,eax
.loop:
        mov     al, [esi]
        lea     esi, [esi+1]
        cmp     al, [edi]
        lea     edi, [edi+1]
        jne     .noteq

        test    al, al
        jnz     .loop

        stc
        pop     edi esi eax
        return

.noteq:
        clc
        pop     edi esi eax
        return
endp


;***************************************************************************************
; Compares two strings - case NOT sensitive.
; Returns CARRY = 1 if the strings are equal.
; Returns CARRY = 0 if the strings are different.
;***************************************************************************************
proc StrCompNoCase, .str1, .str2
begin
        push    eax esi edi
        stdcall StrPtr, [.str1]
        mov     esi,eax
        stdcall StrPtr, [.str2]
        mov     edi,eax

.cmploop:
        mov     al, [esi]
        lea     esi, [esi+1]
        mov     ah, [edi]
        lea     edi, [edi+1]

        test    al,al
        jz      .eos
        test    ah,ah
        jz      .noteq

        or      eax, $2020
        cmp     al, ah
        je      .cmploop

.noteq:
        clc
        pop     edi esi eax
        return

.eos:
        test    ah,ah
        jnz     .noteq

        stc
        pop     edi esi eax
        return
endp



;*******************************************************************************
; Get the text of the [Control] using WM_GETTEXT and put it to the string with
; handle (only) in [string].
;
; if [string] = NULL creates new string and returns the handle.
; if [string] <> NULL just copyes the text.
;*******************************************************************************
proc GetControlText, .Control, .string
;.res dd ?
;.len dd ?
begin
;        push    ebx edi
;        mov     eax,[.string]
;        test    eax,eax
;        jnz     @f
;        stdcall StrNew
;@@:
;        mov     [.res],eax
;        invoke  SendMessage, [.Control], WM_GETTEXTLENGTH, 0, 0
;        add     eax, 16
;        mov     [.len], eax
;        stdcall StrSetLength, [.res], eax
;        test    eax,eax
;        jz      .error
;        invoke  SendMessage, [.Control], WM_GETTEXT, [.len], eax
;        mov     eax,[.res]
;.error:
;        pop     edi ebx
        return
endp

;*******************************************************************************
; Sets the text in control using WM_SETTEXT from string with handle or pointer
; in [string].
;*******************************************************************************
proc SetControlText, .Control, .string
begin
;        stdcall StrPtr, [.string]
;        invoke  SendMessage, [.Control], WM_SETTEXT, 0, eax
        return
endp

;**********************************************************
;  Creates string and assigns it to variable. If variable
;  already contains string handle, the old string will be
;  deleted.
;  Arguments:
;    [ptrHString] - variable containing string handle.
;    ptrSource - pointer to the source for string.
;**********************************************************
; Bug fix 26.09.2003 JohnFound
proc SetString, .ptrHString, .ptrSource
begin
        push    eax esi
        mov     esi, [.ptrHString]

        cmp     dword [esi], 0
        je      @f
        stdcall StrDel, [esi]
@@:
        stdcall StrNew
        mov     [esi], eax
        stdcall StrCopy, eax, [.ptrSource]
        pop     esi eax
        return
endp

;**********************************************************************************
; StrCat appends one string to another
; Arguments:
;   dest - destination string (handle only)
;   source - source string
;**********************************************************************************
proc StrCat, .dest, .source
        begin
        push    eax ebx ecx

	mov eax,[.dest]
	test eax,eax
	jz .exit
        stdcall StrLen, eax ;[.dest]
        mov     ebx,eax                 ; store dest length in ebx

	mov eax,[.source]
;	test eax,eax
;	jz .exit
	cmp eax,400000h
	jnc .exit
;jmp .exit
        stdcall StrLen, eax ;[.source]
        add     eax, ebx
        inc     eax                     ; new dest length
        stdcall StrSetLength, [.dest], eax
        add     ebx, eax                ; calculate end of old dest in ebx
        stdcall StrPtr, [.source]
.copy:
        mov     cl,[eax]
        inc     eax
        mov     [ebx],cl
        inc     ebx
        or      cl,cl
        jnz     .copy
.exit:
        pop     ecx ebx eax
        return
endp


;**********************************************************************************
; StrCharPos returns a pointer to the first occurence of a given char
;   in specified string
; Arguments:
;   Char - char to look for
;   hString -  string to search
; Returns: a pointer to the char in source, or NULL if char doesn't occur
;   in given string
;**********************************************************************************
proc StrCharPos, .hPattern, .char
        begin
        push    esi
        stdcall StrPtr,[.hPattern]
        mov     esi,eax
        mov     eax,[.char]
        xchg    al,ah
     .search:
        mov     al,[esi]
        inc     esi
        or      al,al
        je      .not_found
        cmp     al,ah
        jne     .search
        mov     eax,esi
        dec     eax
        pop     esi
        return
     .not_found:
        xor     eax,eax
        pop     esi
        return
endp


;**********************************************************************************
; StrPos returns a pointer to the first occurence of a pattern string
;   in another string
; Arguments:
;   hPattern - 'pattern' string
;   hString -  string to search
; Returns: a pointer to the pattern string in source , or NULL if pattern string
; doesn't occur in the string to search
;**********************************************************************************
proc StrPos         ; proc StrPos [hString], [hPattern]
        begin
        push    ebx ecx edx esi edi     ; esp = esp -20
        mov     esi,[esp+20+8]          ; mov esi,[hPattern]
        mov     edi,[esp+20+4]          ; mov edi,[hString]
        stdcall StrLen, edi
        mov     ebx,eax                 ; now ebx holds lenght of the string to search
        stdcall StrLen, esi
        mov     edx,eax                 ; now edx holds length of the pattern string
        stdcall StrPtr, esi
        mov     esi,eax                 ; put pointer to the pattern str in esi
        stdcall StrPtr,edi
        mov     edi,eax                 ; put pointer to the search str in edi
        lodsb                           ; load first character of the pattern
        mov     ecx,ebx                 ;
        mov     ebx,edx                 ; put str_len(pattern)-1 in ebx
        dec     ebx                     ;
  .search:
        repne   scasb
        jne     .not_found
        cmp     ecx,ebx
        jb      .not_found
        push    edi esi ecx
        or      ebx,ebx                 ; ebx==0 means that we were searching for one
        jz      .got_it                 ; character. We found it, so we stop.
        mov     ecx,ebx
        repe    cmpsb
        jne     .not_match
  .got_it:
        pop     ecx esi edi
        dec     edi
        mov     eax,edi
  .ret:
        pop     edi esi edx ecx ebx
        ret     8
  .not_match:
        pop     ecx esi edi
        jmp     .search
  .not_found:
        xor     eax,eax
        jmp     .ret
endp

;**********************************************************************************
; StrInsert inserts one string into another at specified pos
; Arguments:
;   dest - destination where the source will be inserted (handle only)
;   source -  string to insert
;**********************************************************************************
proc StrInsert      ; proc StrInsert [dest], [source], [pos]
begin
	push    eax ebx ecx esi edi     ; esp = esp-20
	stdcall StrLen, dword[esp+20+4] ; stdcall StrLen,[dest]
	cmp     eax,dword[esp+20+12]    ; cmp eax,[pos]
	jb      .ret                    ; don't insert a string if pos>StrLen(dest)
	inc     eax
	mov     ecx,eax
	stdcall StrNew                  ; create temporary string...
	mov     ebx,eax                 ; ... and store its handle in ebx
	stdcall StrSetLength, ebx,ecx
	stdcall StrPtr, dword[esp+20+4] ; stdcall StrPtr,[dest]
	mov     esi,eax
	stdcall StrPtr, ebx
	mov     edi,eax
	mov     ecx,dword[esp+20+12]    ; mov ecx,[pos]
	jz      @f
	rep     movsb
  @@:
	stdcall StrCat, ebx,dword[esp+20+8]     ; stdcall StrCat, ebx,[source]
	stdcall StrCat, ebx,esi
	mov     eax,dword[esp+24]
	stdcall StrCopy, eax,ebx
	stdcall StrDel, ebx
  .ret:
	pop     edi esi ecx ebx eax
        ret     12
endp

;**********************************************************************************
; Converts strings to Lower Case
; First parameter = String to Convert to lower case
; corrupts eax
;**********************************************************************************
proc StrLCase               ; proc StrLCase [hString]
        begin
        push    edi
        stdcall StrPtr, [esp+4+1*4]
        xchg    edi, eax
._lowercaseloop:
        mov     al, [edi]
        cmp     al, 'A'
        jc      @F
        cmp     al, 'Z'
        ja      @F
        or      byte[edi], 20h
@@:
        cmp     byte[edi],0
        lea     edi, [edi+1]
        jnz     ._lowercaseloop
        pop     edi
        retn    4
endp


;**********************************************************************************
; Converts strings to Upper Case
; First parameter = String to Convert to upper case
; corrupts eax
;**********************************************************************************
proc StrUCase       ; proc StrUCase [hString]
        begin
        push    edi
        stdcall StrPtr, [esp+4+1*4]
        xchg    edi, eax
._uppercaseloop:
        mov     al, [edi]
        cmp     al, 'a'
        jc      @F
        cmp     al, 'z'
        ja      @F
        sub     byte[edi], 20h
@@:
        cmp     byte[edi], 0
        lea     edi, [edi+1]
        jnz     ._uppercaseloop
        pop     edi
        retn    4
endp

;**********************************************************************************
; String copy mmx version for long strings does by copying qword at a time
; First parameter = String destination
; Second parameter = String source
;**********************************************************************************
proc StrCopyMMX     ; proc StrCopyMMX [str1], [str2]
        begin
        push    eax
        push    ebx
        push    ecx
        push    edx
        stdcall StrPtr, [esp+4+4*4]
        xchg    eax, edx
        stdcall StrPtr, [esp+8+4*4]
        mov     ecx,eax
        stdcall StrLen,eax
        stdcall StrSetLength, [esp+4+4*4+4], eax
        mov     eax,ecx
        xor     ecx,ecx
        pxor    MM7, MM7
.MMX_loop:
        movq    MM0, [eax+ecx]
        movq    MM1, MM0
        pcmpeqb MM0, MM7
        packsswb MM0, MM0
        movd    ebx, MM0
        test    ebx, ebx
        jnz     .normal_copy
        movq    [edx+ecx], MM1
        add     ecx, 8
        jmp     .MMX_loop
.normal_copy:
        mov     bl, [eax+ecx]
        mov     [edx+ecx], bl
        inc     ecx
        test    bl, bl
        jnz     .normal_copy
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax
        retn    8
endp

;**********************************************************************************
; StrExtract copies the part of [str] from [index] with lenght in [len]
; Returns handle to new created string.
;**********************************************************************************
proc StrExtract, string,index,len
        begin
        push    ebx ecx esi edi
        stdcall StrLen,[.string]
        cmp     eax,[.index]
        jbe     .error
        stdcall StrNew
        mov     ebx,eax
        mov     ecx,[.len]
        stdcall StrSetLength, eax,ecx   ; stdcall StrSetLength, eax,[len]
        stdcall StrPtr, eax
        mov     edi,eax
        stdcall StrPtr, [.string]
        add     eax,[.index]
        mov     esi,eax
    @@: mov     al,[esi]
        mov     [edi],al
        inc     esi
        inc     edi
        or      al,al
        jz      .copied
        dec     ecx
        jnz     @b
        xor     al,al
        mov     [edi],al
  .copied:
        mov     eax,ebx
        pop     edi esi ecx ebx
        return
  .error:
        xor     eax,eax
        pop     edi esi ecx ebx
        return
endp

;**********************************************************************************
; _NumToStr converts the number in eax to the string in any radix approx. [2..26]
; Arguments:
;   [edi] - pointer to the string buffer
;   ecx - radix
;   eax - number to convert.
; There is no parameter check, so be careful.
; returns: edi points to the end of a converted number
;**********************************************************************************
proc _NumToStr
begin
    test  eax,eax
    jns   _NumToStrU
    neg   eax
    mov   byte [edi],"-"
    inc   edi
endp

proc _NumToStrU
begin
    cmp   eax,ecx
    jb    .lessA
    xor   edx,edx
    div   ecx
    push  edx
    call  _NumToStrU
    pop   eax
.lessA:
    cmp   al, 10
    sbb   al, 69h
    das
    stosb
    return
endp

;*****************************************************
; NumToStrF:
;   Converts signed integer value to string.
; NumToStrUF:
;   Converts unsigned integer value to string.
;
; edi - pointer to string buffer
; eax - Number to convert
; ecx - radix from 2 to $ff
; esi - length of the number in chars
;
; returns: edi - pointer to the end of converted num
;
; Note: Don't use 1 as radix.
;*****************************************************
proc NumToStrF
begin
        test    eax,eax
        jns     NumToStrUF
        neg     eax
        mov     byte [edi],'-'
        push    esi
        dec     esi
        add     edi,esi
        push    edi
        jmp     NumToStrUF.loopc
endp

proc NumToStrUF
        begin
        push    esi
        add     edi, esi
        push    edi
        dec     edi
.loopc:
        xor     edx,edx
        div     ecx
        xchg    al,dl
        cmp     al,$0a
        sbb     al,$69
        das
        mov     [edi],al
        dec     edi
        xchg    al,dl
        dec     esi
        jnz     .loopc
        pop     edi
        pop     esi
        return
endp


;***********************************************************
; NumToStr - converts number to any radix.
; num - number to convert
; str - handle of the string. If NULL - creates new string.
; index - Offset in string where to put converted number.
; flags:
;   byte 0 - number of digits if ntsFixedWidth is set.
;   byte 1 - contains radix for the convertion.
;   byte 2,3 - flags.
; Returns:
;   eax - handle of the string (new one or passed in [str])
;   edx - pointer to the string.
;***********************************************************
proc NumToStr, .num, .strng, .index, .flags
.max_len dd ?
.len_tab rb 20      ; table that holds maximum number of digits in given system
        begin
        push    ebx ecx edx esi edi

        mov     [.len_tab+2],32+2       ; binary number - max. 32 digits (+2 bytes for '-' character and NULL terminator)
        mov     [.len_tab+4],16+2       ; quad number - max. 16 digits
        mov     [.len_tab+8],11+2       ; octal number - max. 11 digits
        mov     [.len_tab+10],9+2       ; decimal number - max. 9 digits
        mov     [.len_tab+16],8+2       ; hexadecimal number - max. 8 digits
        movzx   ebx,byte [.flags+1]     ; load radix into ebx
        movzx   eax,byte[.len_tab+ebx]
        mov     [.max_len],eax          ; store max. number of digits
        mov     eax,[.strng]
        or      eax,eax
        jnz     .string_exists
        stdcall StrNew
        mov     [.strng],eax
  .string_exists:
        test    eax,0xffff0000
        jnz     @f                      ; don't resize if [str] isn't a handle
        mov     ebx,eax
        mov     eax,[.max_len]
        add     eax,[.index]
        mov     edx,eax
        stdcall StrLen,ebx
        cmp     eax,edx
        ja      @f                      ; don't resize string if it has enough place for converted number

        stdcall StrSetLength,ebx, edx

  @@:   ; determine which conversion func to use
        mov     eax,[.flags]
        mov     edx,eax
        and     eax,ntsFixedWidth
        jnz     .fixed_width
        mov     eax,edx
        and     eax,ntsUnsigned
        jnz     .unsigned
        mov     ebx,_NumToStr
        jmp     .got_func
  .unsigned:
        mov     ebx,_NumToStrU
        jmp     .got_func

  .fixed_width:
        movzx   esi, byte [.flags]         ; load fixed width into esi
        mov     eax, edx
        and     eax, ntsUnsigned
        jnz     .fixed_unsigned
        mov     ebx,NumToStrF
        jmp     .got_func
  .fixed_unsigned:
        mov     ebx,NumToStrUF
  .got_func:
        stdcall StrPtr, [.strng]
        add     eax,[.index]
        mov     edi,eax
        movzx   ecx,byte [.flags+1]       ; load radix into ecx
        mov     eax, [.num]
        call    ebx                     ; call low-level convertion routine
        mov     eax, [.flags]
        and     eax, ntsZeroTerminated
        jz      .ret
        mov     byte [edi], 0

  .ret:
        pop     edi esi edx ecx ebx
        mov     eax, [.strng]
        return
endp


;-------------------------------------------------
; function StrHash
;   Computes 32 bit hash value from the string.
;   The function is compatible with FASM hash
;   function if OrMask = 0.
;
; Arguments:
;   hString - handle/pointer of the string.
;   OrMask  - every byte from the string will be ORed
;             with this value (byte)
; Return:
;   eax - 32bit hash value.
;-------------------------------------------------
proc StrHashOld, .hString, .OrMask
begin
        push    esi ebx ecx

        stdcall StrPtr, [.hString]
        mov     esi, eax

        xor     ebx, ebx
        xor     eax, eax
.hashloop:
        rol     eax, 12
        mov     ecx, eax
        shr     eax, 8
        and     ecx, 1111b
        or      eax, ecx
        movzx   ecx, byte [esi+ebx]
        jecxz   .endloop
        or      cl, byte [.OrMask]
        add     eax, ecx
        inc     ebx
        jmp     .hashloop

.endloop:
        and     eax,0FFFFFFh
        shl     ebx,24
        or      eax,ebx
        pop     ecx ebx esi
        return
endp


proc StrHash, .hString, .OrMask
begin
        push    esi edi ebx ecx edx

        stdcall StrPtr, [.hString]
        mov     esi, eax

        xor     ebx, ebx
        mov     eax,2166136261          ; FNV offset basis
        xor     ecx, ecx
        mov     edi, 16777619                ; FNV prime
.hashloop:
        mov     cl, [esi+ebx]
        jecxz   .endstring
        inc     bl
        or      cl, byte [.OrMask]
        xor     al,cl                   ; FNV-1a hashing
        mul     edi
        jmp     .hashloop

.endstring:
        mov     edx,eax
        and     eax,0FFFFFFh            ; xor-fold to 24 bits
        shr     edx,24
        xor     eax,edx
        shl     ebx,24                  ; the length of the string
        or      eax,ebx                 ; store length in high 8 bits
        pop     edx ecx ebx edi esi
        return
endp



;-------------------------------------------------------
; function StrToNum
;   Converts specified string into a number
;
; Arguments:
;   hString - handle/pointer of the string containing
;     number to convert. It doesn't have to be ended by
;     NULL, any other character will stop conversion.
;     Number to convert must be decimal.
;
; Return:
;   eax - converted number
;
; Note: in case of failture (first char of given pointer
;   isn't a number) function returns -1.
;-------------------------------------------------------
proc StrToNum, .hString
        begin
        push    ebx edx esi
        xor     ebx,ebx         ; ebx will store our number
        stdcall StrPtr, [.hString]
        mov     esi,eax
        xor     eax,eax
        mov     al,[esi]
        cmp     al,'0'
        jb      .error
        cmp     al,'9'
        jbe     .digit
        jmp     .error
     .digit:
        sub     al,'0'
        add     ebx,eax
        inc     esi
        mov     al,[esi]
        cmp     al,'0'
        jb      .finish
        cmp     al,'9'
        ja      .finish
        mov     edx,ebx         ; multiply ebx by 10
        shl     ebx,3
        add     ebx,edx
        add     ebx,edx
        jmp     .digit
     .finish:
        mov     eax,ebx
        pop     esi edx ebx
        return

     .error:
        xor     eax,eax
        dec     eax
        pop     esi edx ebx
        return
endp

;-------------------------------------------------------
; function StrCharCat
;   Addes specified char to the end of a string
;
; Arguments:
;   hString - string to append
;   char - char to add
;-------------------------------------------------------
proc StrCharCat, .hString, .char
        begin
        push    eax ebx ecx
        mov     ebx, [.hString]
        stdcall StrLen, ebx
        mov     ecx, eax
        inc     eax
        stdcall StrSetLength, ebx, eax
;        stdcall StrPtr, ebx                    ; StrSetLength returns a pointer to the string
        add     ecx, eax
        mov     eax, [.char]
        mov     [ecx], al
        inc     ecx
        mov     byte [ecx], 0
        pop     ecx ebx eax
        return
endp

;------------------------------------------------------------
; function StrInsertChar
;   Inserts specified char into given position ot the string
;
; Arguments:
;   hString - string to append
;   char    - char to add
;   pos     - position where to add the char
;-------------------------------------------------------------
proc StrInsertChar, .hString, .char, .pos
        begin
        push    eax ebx ecx
        mov     ebx, [.hString]
        stdcall StrLen, ebx
        cmp     eax, [.pos]
        jb      .finish
        je      .append
        mov     ecx,eax
        inc     eax
        stdcall StrSetLength, ebx, eax
        stdcall StrPtr, ebx
        add     ecx, eax
        mov     ebx, eax
        add     ebx, [.pos]
        mov     byte [ecx+1], 0     ; add NULL-terminator in the end of our string
  .shift:
        mov     al, [ecx-1]
        mov     [ecx], al
        dec     ecx
        cmp     ecx, ebx
        jne     .shift
        mov     eax, [.char]
        mov     [ebx], al
  .finish:
        pop     ecx ebx eax
        return
  .append:
        stdcall StrCharCat, ebx, [.char]
        jmp     .finish
endp





proc StrURLEncode, .hstr
.res dd ?
begin
        push    ebx ecx edx esi edi
        stdcall StrPtr, [.hstr]
        mov     esi, eax

        stdcall StrLen, esi
        mov     ecx, eax
        lea     edx, [2*eax+eax]        ; the encoded string can be max 3x long as original string.

        stdcall StrNew
        mov     [.res], eax
        jecxz   .finish

        stdcall StrSetLength, eax, edx
        mov     edi, eax
        xor     edx, edx
        xor     ebx, ebx

.encode:
        lodsb
        cmp     al, $80
        jae     .store          ; it is a hack, but I hope save enough.

        mov     dl, al
        mov     bl, al
        shr     edx, 5
        and     ebx, $1f
        bt      dword [URLCharTable+4*edx], ebx
        jnc     .store

        mov     ah, al
        mov     al, '%'
        stosb
        mov     al, ah
        shr     al, 4
        cmp     al, $0a
        sbb     al, $69
        das
        stosb
        mov     al, ah
        and     al, $0f
        cmp     al, $0a
        sbb     al, $69
        das

.store:
        stosb
        loop    .encode
.end:
        xor     al, al
        stosb
.finish:
        mov     eax, [.res]
        pop     edi esi edx ecx ebx
        return
endp

; Contains 1 where the character must be % encoded and 0 where it is save to pass it directly
URLCharTable db 11111111b       ;
             db 11111111b       ;
             db 11111111b       ;
             db 11111111b       ; 0..31 -control chars | encoded
             db 11111111b       ; $27 - $20: '&%$#"!   | encoded
             db 11111111b       ; $2f - $28: /.-,+*)(  | encoded
             db 00000000b       ; $37 - $30: 76543210  | not encoded
             db 11111100b       ; $3f - $38: ?>=<;:98  | partially
             db 00000001b       ; $47 - $40: GFEDCBA@  | partially
             db 00000000b       ; $4f - $48: ONMLKJIH  | not encoded
             db 00000000b       ; $57 - $50: WVUTSRQP  | not encoded
             db 11111000b       ; $5f - $58: _^]\[ZYX  | partially
             db 00000001b       ; $67 - $60: gfedcba`  | partially
             db 00000000b       ; $6f - $68: onmlkjih  | not encoded
             db 00000000b       ; $77 - $70: wvutsrqp  | not encoded
             db 11111000b       ; $7f - $78:  ~}|{zyx  | partially
